package com.kmxd.ams.app.system;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.kmxd.ams.client.system.ISysDictItemSvc;
import com.kmxd.ams.client.system.ISysDictSetSvc;
import com.kmxd.ams.client.system.ISysDictSvc;
import com.kmxd.ams.client.system.dto.SysDictAddDTO;
import com.kmxd.ams.client.system.dto.SysDictEditDTO;
import com.kmxd.ams.client.system.dto.SysDictItemQueryDTO;
import com.kmxd.ams.client.system.dto.SysDictQueryDTO;
import com.kmxd.ams.client.system.vo.SysDictItemListVO;
import com.kmxd.ams.client.system.vo.SysDictItemVO;
import com.kmxd.ams.client.system.vo.SysDictListVO;
import com.kmxd.ams.client.system.vo.SysDictVO;
import com.kmxd.ams.core.common.bo.DictModel;
import com.kmxd.ams.core.common.constant.CacheConst;
import com.kmxd.ams.core.common.constant.Constants;
import com.kmxd.ams.core.common.constant.ErrorCode;
import com.kmxd.ams.core.common.enums.DictValueEnum;
import com.kmxd.ams.core.common.enums.StatusEnum;
import com.kmxd.ams.core.exception.BizTipException;
import com.kmxd.ams.core.util.IdUtil;
import com.kmxd.ams.core.util.SecurityUtil;
import com.kmxd.ams.infra.system.entity.SysDict;
import com.kmxd.ams.infra.system.entity.SysDictItem;
import com.kmxd.ams.infra.system.entity.SysDictSet;
import com.kmxd.ams.infra.system.mapper.SysDictMapper;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 字典表 服务实现类
 *
 * @author admin
 * @since 2021-06-10
 */
@Service
@AllArgsConstructor
@Slf4j
@CacheConfig(cacheNames = CacheConst.CACHE_PREFIX_SYS_DICT)
public class SysDictSvcImpl extends ServiceImpl<SysDictMapper, SysDict> implements ISysDictSvc {

  private SysDictMapper sysDictMapper;
  private ISysDictItemSvc sysDictItemSvc;
  private ISysDictSetSvc sysDictSetSvc;

  @Cacheable(keyGenerator = "myKeyGenerator")
  @Override
  public Page<SysDictListVO> selectSysDictWithPage(Page reqPage, SysDictQueryDTO req) {
    LambdaQueryWrapper<SysDict> queryWrapper = Wrappers.lambdaQuery();
    queryWrapper.like(
        StrUtil.isNotBlank(req.getDictCode()), SysDict::getDictCode, req.getDictCode());
    queryWrapper.like(
        StrUtil.isNotBlank(req.getDictName()), SysDict::getDictName, req.getDictName());
    queryWrapper.orderByDesc(SysDict::getCreateTime);
    Page<SysDict> page = sysDictMapper.selectPage(reqPage, queryWrapper);
    Page<SysDictListVO> voPage = BeanUtil.copyProperties(page, Page.class);
    voPage.setRecords(BeanUtil.copyToList(page.getRecords(), SysDictListVO.class));
    return voPage;
  }

  @Cacheable(keyGenerator = "myKeyGenerator")
  @Override
  public Page<SysDictListVO> selectPage(Page reqPage, SysDictQueryDTO req) {
    LambdaQueryWrapper<SysDict> queryWrapper = Wrappers.lambdaQuery();
    queryWrapper.like(
        StrUtil.isNotBlank(req.getDictCode()),
        SysDict::getDictCode,
        Optional.ofNullable(req.getDictCode()).orElse("").toLowerCase());
    queryWrapper.like(
        StrUtil.isNotBlank(req.getDictName()), SysDict::getDictName, req.getDictName());
    queryWrapper.eq(
        ObjectUtil.isNotNull(req.getHaveSystem()), SysDict::getHaveSystem, req.getHaveSystem());
    queryWrapper.orderByDesc(SysDict::getCreateTime);
    Page<SysDict> page = sysDictMapper.selectPage(reqPage, queryWrapper);
    Page<SysDictListVO> voPage = BeanUtil.copyProperties(page, Page.class);
    voPage.setRecords(BeanUtil.copyToList(page.getRecords(), SysDictListVO.class));
    return voPage;
  }

  @Cacheable(keyGenerator = "myKeyGenerator")
  @Override
  public List<SysDictListVO> selectList(SysDictQueryDTO req) {
    LambdaQueryWrapper<SysDict> queryWrapper = Wrappers.lambdaQuery();
    queryWrapper.like(
        StrUtil.isNotBlank(req.getDictCode()),
        SysDict::getDictCode,
        Optional.ofNullable(req.getDictCode()).orElse("").toLowerCase());

    queryWrapper.like(
        StrUtil.isNotBlank(req.getDictName()), SysDict::getDictName, req.getDictName());
    queryWrapper.orderByDesc(SysDict::getCreateTime);
    List<SysDict> list = sysDictMapper.selectList(queryWrapper);

    return BeanUtil.copyToList(list, SysDictListVO.class);
  }

  @Cacheable(keyGenerator = "myKeyGenerator")
  @Override
  public List<SysDictListVO> selectListAll() {
    List<SysDict> dicts = this.list();
    return BeanUtil.copyToList(dicts, SysDictListVO.class);
  }

  @CacheEvict(allEntries = true)
  @Override
  @Transactional(rollbackFor = Exception.class)
  public void add(SysDictAddDTO req) {
    if (!this.checkUniqueDictCode(req.getDictCode(), null)) {
      throw BizTipException.instance(ErrorCode.VALIDATE_ERROR, "字典编码已存在");
    }
    SysDict entity = BeanUtil.copyProperties(req, SysDict.class);
    sysDictMapper.insert(entity);
  }

  @CacheEvict(allEntries = true)
  @Override
  @Transactional(rollbackFor = Exception.class)
  public void edit(SysDictEditDTO req) {
    if (!this.checkUniqueDictCode(req.getDictCode(), req.getId())) {
      throw BizTipException.instance(ErrorCode.VALIDATE_ERROR, "字典编码已存在");
    }
    SysDict entity = BeanUtil.copyProperties(req, SysDict.class);
    sysDictMapper.updateById(entity);
  }

  @CacheEvict(allEntries = true)
  @Override
  @Transactional(rollbackFor = Exception.class)
  public void remove(String ids) {
    Set<Long> idList = IdUtil.split(ids);
    idList.forEach(item -> sysDictItemSvc.removeByDictId(item));
    sysDictMapper.deleteBatchIds(idList);
  }

  @Cacheable(keyGenerator = "myKeyGenerator")
  @Override
  public SysDictVO view(Long id) {
    SysDict entity = sysDictMapper.selectById(id);
    return BeanUtil.copyProperties(entity, SysDictVO.class);
  }

  @Cacheable(keyGenerator = "myKeyGenerator")
  @Override
  public List<DictModel> listDictModel(
      String dictCode,
      String setCode,
      Integer year,
      DictValueEnum type,
      String ignoreValue,
      Long comId) {
    List<SysDict> sysDicts =
        this.list(
            Wrappers.lambdaQuery(SysDict.class)
                .eq(SysDict::getDictCode, dictCode)
                .eq(StrUtil.isNotBlank(setCode), SysDict::getComId, comId));
    List<SysDict> sysDicts2 =
        sysDicts.stream()
            .filter(
                item ->
                    BooleanUtil.isTrue(item.getHaveSystem())
                        || ObjectUtil.equals(item.getComId(), comId))
            .collect(Collectors.toList());
    SysDict sysDict = sysDicts2.size() > 0 ? sysDicts2.get(0) : null;
    if (Objects.isNull(sysDict)) {
      return Lists.newArrayList();
    }
    SysDictItemQueryDTO qryItem = new SysDictItemQueryDTO();
    qryItem.setDictId(sysDict.getId());
    qryItem.setStatus(StatusEnum.YES.getCode());
    if (StrUtil.isNotBlank(setCode)) {
      qryItem.setSetCode(setCode);
    }

    List<SysDictItemListVO> sysDictItemList = sysDictItemSvc.selectList(qryItem);
    Integer version = Objects.isNull(year) ? Constants.DEF_VERSION : year;
    List<SysDictItemListVO> dictItemByVersionList =
        filterDictItemByVersion(
            sysDictItemList,
            item -> (Objects.nonNull(item.getVersion()) && version >= item.getVersion()));
    String ignore = Optional.ofNullable(ignoreValue).orElse("");
    List<String> ignoreList = StrUtil.split(ignore, ",");
    List<DictModel> dictModelList =
        dictItemByVersionList.stream()
            .filter(item -> !ignoreList.contains(item.getDictItemCode()))
            .map(
                item -> {
                  DictModel dictModel = new DictModel();
                  dictModel.setId(String.valueOf(item.getId()));
                  dictModel.setLabel(item.getDictItemName());
                  dictModel.setExt1(item.getExt1());
                  if (null != type) {
                    dictModel.setValue(type.convert(item.getDictItemCode()));
                  } else {
                    dictModel.setValue(item.getDictItemCode());
                  }
                  return dictModel;
                })
            .collect(Collectors.toList());
    return dictModelList;
  }

  /**
   * 按版本过滤字典项
   *
   * @param sysDictItemList
   * @param predicate
   */
  private List<SysDictItemListVO> filterDictItemByVersion(
      List<SysDictItemListVO> sysDictItemList, Predicate<SysDictItemListVO> predicate) {
    if (ObjectUtil.isEmpty(sysDictItemList)) {
      return Lists.newArrayList();
    }

    // 默认版本查询
    long count = sysDictItemList.stream().filter(item -> Objects.isNull(item.getVersion())).count();
    if (sysDictItemList.size() == count) {
      return sysDictItemList;
    }

    Map<Integer, List<SysDictItemListVO>> group =
        sysDictItemList.stream()
            .filter(predicate)
            .sorted(Comparator.comparing(SysDictItemListVO::getVersion).reversed())
            .collect(
                Collectors.groupingBy(
                    SysDictItemListVO::getVersion, LinkedHashMap::new, Collectors.toList()));
    if (group.isEmpty()) {
      return Lists.newArrayList();
    }
    return group.entrySet().iterator().next().getValue();
  }

  @Cacheable(keyGenerator = "myKeyGenerator")
  @Override
  public List<DictModel<String>> listDictSeries(String dictCode, Long comId) {
    if (Objects.isNull(comId)) {
      comId = SecurityUtil.getCropId();
    }
    SysDict sysDict =
        sysDictMapper.selectOne(
            Wrappers.lambdaQuery(SysDict.class)
                .eq(SysDict::getDictCode, dictCode)
                .eq(SysDict::getComId, comId));
    if (Objects.isNull(sysDict)) {
      return Lists.newArrayList();
    }

    SysDictSet qrySet = new SysDictSet();
    qrySet.setDictId(sysDict.getId());
    List<SysDictSet> setList = sysDictSetSvc.selectList(qrySet);

    return setList.stream()
        .map(
            item -> {
              DictModel<String> dictModel = new DictModel<>();
              dictModel.setId(String.valueOf(item.getId()));
              dictModel.setLabel(item.getName());
              dictModel.setValue(item.getCode());
              return dictModel;
            })
        .collect(Collectors.toList());
  }

  @Cacheable(keyGenerator = "myKeyGenerator")
  @Override
  public DictModel dictModel(Long dictItemId, DictValueEnum type) {
    SysDictItemVO item =
        Optional.ofNullable(sysDictItemSvc.view(dictItemId)).orElse(new SysDictItemVO());
    DictModel dictModel = new DictModel();
    dictModel.setId(String.valueOf(item.getId()));
    dictModel.setLabel(item.getDictItemName());
    if (null != type) {
      dictModel.setValue(type.convert(item.getDictItemCode()));
    } else {
      dictModel.setValue(item.getDictItemCode());
    }
    return dictModel;
  }

  @Cacheable(keyGenerator = "myKeyGenerator")
  @Override
  public List<SysDictItemListVO> selectItemList(
      Long comId, String dictCode, String dictItemSetCode) {
    SysDict sysDict =
        sysDictMapper.selectOne(
            Wrappers.lambdaQuery(SysDict.class)
                .eq(SysDict::getDictCode, dictCode)
                .eq(ObjectUtil.isNotNull(comId), SysDict::getComId, comId));
    if (Objects.isNull(sysDict)) {
      return Lists.newArrayList();
    }
    SysDictItemQueryDTO qryItem = new SysDictItemQueryDTO();
    qryItem.setDictId(sysDict.getId());
    if (StrUtil.isNotBlank(dictItemSetCode)) {
      qryItem.setSetCode(dictItemSetCode);
    }
    return sysDictItemSvc.selectList(qryItem);
  }

  @Cacheable(keyGenerator = "myKeyGenerator")
  @Override
  public List<SysDictItemListVO> getDictItemList(String dictCode) {
    SysDict sysDict =
        sysDictMapper.selectOne(
            Wrappers.lambdaQuery(SysDict.class).eq(SysDict::getDictCode, dictCode));
    if (Objects.isNull(sysDict)) {
      return Lists.newArrayList();
    }
    SysDictItemQueryDTO qryItem = new SysDictItemQueryDTO();
    qryItem.setDictId(sysDict.getId());
    return sysDictItemSvc.selectList(qryItem);
  }

  @Cacheable(keyGenerator = "myKeyGenerator")
  @Override
  public SysDictItemVO getDictItem(
      @NotNull String dictCode,
      String dictItemCode,
      String dictItemName,
      String dictItemSetCode,
      Long comId) {
    SysDict sysDict =
        sysDictMapper.selectOne(
            Wrappers.lambdaQuery(SysDict.class)
                .eq(SysDict::getDictCode, dictCode)
                .eq(SysDict::getComId, comId));
    if (Objects.isNull(sysDict)) {
      return null;
    }
    SysDictItem one =
        sysDictItemSvc.getOne(
            Wrappers.<SysDictItem>lambdaQuery()
                .eq(SysDictItem::getDictId, sysDict.getId())
                .eq(StrUtil.isNotBlank(dictItemCode), SysDictItem::getDictItemCode, dictItemCode)
                .eq(StrUtil.isNotBlank(dictItemName), SysDictItem::getDictItemName, dictItemName)
                .eq(StrUtil.isNotBlank(dictItemSetCode), SysDictItem::getSetCode, dictItemSetCode));
    return BeanUtil.copyProperties(one, SysDictItemVO.class);
  }

  @CacheEvict(allEntries = true)
  @Override
  public void clearAllCache() {
    log.info("清除数据字典缓存");
  }

  private Boolean checkUniquedictName(String value, Long id) {
    if (StrUtil.isBlank(value)) {
      return true;
    }
    id = Objects.isNull(id) ? -1L : id;
    SysDict entity =
        sysDictMapper.selectOne(
            Wrappers.lambdaQuery(SysDict.class).eq(SysDict::getDictName, value));
    return Objects.isNull(entity) || NumberUtil.equals(entity.getId(), id);
  }

  private Boolean checkUniqueDictCode(String value, Long id) {
    if (StrUtil.isBlank(value)) {
      return true;
    }
    id = Objects.isNull(id) ? -1L : id;
    SysDict entity =
        sysDictMapper.selectOne(
            Wrappers.lambdaQuery(SysDict.class).eq(SysDict::getDictCode, value.toLowerCase()));
    return Objects.isNull(entity) || NumberUtil.equals(entity.getId(), id);
  }
}
